package com.nightpiter.view;

import static com.nightpiter.controller.log.Logger.log;

import java.util.ArrayList;
import java.util.List;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Handler;
import android.view.Window;
import android.widget.Toast;
import android.widget.LinearLayout.LayoutParams;

import com.nightpiter.NightPiter;
import com.nightpiter.R;
import com.nightpiter.controller.MetroController;
import com.nightpiter.controller.algorithms.MetroRouterState;
import com.nightpiter.controller.algorithms.Route;
import com.nightpiter.controller.log.Logger;
import com.nightpiter.controller.newnet.NetLister;
import com.nightpiter.controller.newnet.Resource;
import com.nightpiter.controller.newnet.Resource.Type;
import com.nightpiter.controller.reflection.Module;
import com.nightpiter.model.MetroModel;
import com.nightpiter.model.MetroModelData;
import com.nightpiter.model.MetroModelState;
import com.nightpiter.model.objects.Station;
import com.nightpiter.view.dialogs.AboutDialog;
import com.nightpiter.view.dialogs.BaseDialog;
import com.nightpiter.view.dialogs.ClassesDialog;
import com.nightpiter.view.dialogs.DrawerOptionsDialog;
import com.nightpiter.view.dialogs.FilesDialog;
import com.nightpiter.view.dialogs.InfoDialog;
import com.nightpiter.view.dialogs.ItemsDialog;
import com.nightpiter.view.dialogs.LoaderDialog;
import com.nightpiter.view.dialogs.NetDialog;
import com.nightpiter.view.drawer.MetroDrawer;
import com.nightpiter.view.drawer.MetroDrawerWrapper;

/**
 * MVC's View class
 * 
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 */
public class MetroView implements MetroDrawerWrapper.PreparingListener,
		Screen.Listener, InfoDialog.Listener, FilesDialog.Listener,
		ClassesDialog.Listener, NetLister.Listener, Resource.Listener,
		DrawerOptionsDialog.Listener {

	/** Application context */
	private NightPiter context;

	/** the drawer */
	private MetroDrawerWrapper drawer = MetroDrawerWrapper.getInstance();

	/** our classic screen instance */
	private Screen oldScreen;

	/** new screen drawing above the world map */
	private Screen mapScreen;

	/** a screen currently used to show the map */
	private Screen currentScreen;

	/** metro loader dialog */
	private LoaderDialog loaderDialog;

	/** files lister dialog */
	private FilesDialog mapFilesDialog;

	/** expandable lister dialog */
	private ClassesDialog drawerClassesDialog;

	/** dialog to show stations and routes info */
	private InfoDialog infoDialog;

	/** about application dialog */
	private AboutDialog aboutDialog;

	private NetDialog netDialog;

	private DrawerOptionsDialog drawerOptionsDialog;

	/** short message to show if some function is not yet implemented */
	@SuppressWarnings("unused")
	private Toast notYetImplementedToast;

	/**
	 * default constructor
	 * 
	 * @param context
	 *            the application context
	 */
	public MetroView(NightPiter context) {
		this.context = context;
		log("View instance created");
	}

	/**
	 * should be called after another MVC components created
	 */
	public void init() {
		log("View init() call");

		this.context.requestWindowFeature(Window.FEATURE_PROGRESS);

		this.context.setProgressBarVisibility(false);
		this.context.setProgress(0);
		this.context.setProgressBarIndeterminate(false);

		this.loaderDialog = new LoaderDialog(context);

		this.mapFilesDialog = new FilesDialog(context);
		this.mapFilesDialog.setListener(this);
		this.mapFilesDialog.setHeader(R.string.maps_title,
				R.drawable.openmap_small);

		this.drawerClassesDialog = new ClassesDialog(context);
		this.drawerClassesDialog.setListener(this);
		this.drawerClassesDialog.setHeader(R.string.drawers_title,
				R.drawable.ds);

		this.infoDialog = new InfoDialog(context);
		this.infoDialog.setListener(this);

		this.aboutDialog = new AboutDialog(context);

		this.netDialog = new NetDialog(context);
		this.netDialog.setListener(this);

		this.drawerOptionsDialog = new DrawerOptionsDialog(context);
		this.drawerOptionsDialog.setListener(this);

		this.mapScreen = new MetroOverlay(context);
		this.mapScreen.setListener(this);

		this.oldScreen = new MetroScreenView(context);
		this.oldScreen.setListener(this);

		this.setScreen(this.oldScreen);

		this.notYetImplementedToast = Toast.makeText(context,
				"this function is currently disabled", Toast.LENGTH_LONG);

	}

	private void setScreen(Screen screen) {
		this.currentScreen = screen;
		screen.prepare();
		this.context.setContentView(screen.getView());
		LayoutParams layoutParams = new LayoutParams(LayoutParams.FILL_PARENT,
				LayoutParams.FILL_PARENT);
		this.context.addContentView(infoDialog, layoutParams);
		drawer.prepare(null, getModel().getData(), screen.getMode(), null);
	}

	/**
	 * set the application's title bar text
	 * 
	 * @param action
	 */
	public void setApplicationTitle(String action) {
		MetroModelData modelData = getModel().getData();
		String title;
		String appName = getContext().getString(R.string.app_name);
		if (action == null) {
			this.context.setProgressBarVisibility(false);
			if (modelData.getCityName().length() != 0) {
				title = appName + " - " + modelData.getCityName();
			} else {
				title = appName;
			}
		} else {
			this.context.setProgressBarVisibility(true);
			title = appName + " :: " + action;
		}
		getContext().setTitle(title);
	}

	public void onDrawerPropertiesChanged() {
		getCurrentScreen().redraw(true);
	}

	/**
	 * called when change the drawer menu item selected
	 */
	public void onChangeDrawerMenuItemSelected() {
		List<String> drawersList = getController().getPacksList();
		this.drawerClassesDialog.show(drawersList);
	}

	/**
	 * called when open new map menu item selected
	 */
	public void onOpenFileMenuItemSelected() {
		List<String> mapsList = getController().getMapsList();
		this.mapFilesDialog.show(mapsList);
	}

	public void onDownloadButtonClick(BaseDialog dialog) {
		if (dialog == this.mapFilesDialog) {
			getController().obtainMapsFromNet(this);

		} else if (dialog == this.drawerClassesDialog) {
			getController().obtainDrawersFromNet(this);

		}
	}

	public void onObtainingFailed() {
		Toast.makeText(context, "error fetching resources list",
				Toast.LENGTH_SHORT).show();
		setApplicationTitle(null);
	}

	public void onObtainingStart() {
		setApplicationTitle("Fetching resources");
	}

	public void onResorcesObtained(ArrayList<Resource> resources) {
		setApplicationTitle(null);
		this.netDialog.show(resources);
	}

	@SuppressWarnings("unchecked")
	public void onChildSelected(ClassesDialog itemsDialog, Module module) {
		if (itemsDialog == this.drawerClassesDialog) {
			this.infoDialog.hide();
			Class theClass = module.load();
			if (theClass == null) {
				log("error exrtacting class. null was retrieved from Controller");
				Toast.makeText(
						context,
						"error exrtacting class."
								+ "\nseems like you file is corrupted",
						Toast.LENGTH_LONG).show();
			} else if (!MetroDrawer.class.isAssignableFrom(theClass)) {
				log("exrtacted class is not a subclass of MetroDrawer");
				Toast.makeText(context,
						"exrtacted class is not a subclass of MetroDrawer",
						Toast.LENGTH_LONG).show();
			} else {
				drawer.prepare(theClass, getModel().getData(), currentScreen
						.getMode(), this);
			}
		}
	}

	public void onGroupSelected(ClassesDialog itemsDialog,
			final String groupName) {
		getController().post(new Runnable() {
			public void run() {
				drawerClassesDialog.onChildrenRetrieved(groupName,
						getController().getPack(
								context.getString(R.string.drawers_dir) + "/"
										+ groupName));
			}
		});
	}

	/**
	 * user selected a file from the list
	 * 
	 * @see com.nightpiter.view.dialogs.FilesDialog.Listener#onItemSelected(com.nightpiter.view.dialogs.FilesDialog,
	 *      java.lang.String)
	 */
	@SuppressWarnings("unchecked")
	public void onItemSelected(ItemsDialog itemsDialog, Object itemName) {
		if (itemsDialog == this.mapFilesDialog) {
			this.infoDialog.hide();
			getController().loadModel(
					context.getString(R.string.maps_dir) + "/" + itemName);

		} else if (itemsDialog == this.netDialog) {
			this.infoDialog.hide();
			((Resource) itemName).download(getContext(), this);

		}
	}

	public void onDownloadCompleted(final Resource resource) {
		setApplicationTitle(null);
		DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int button) {
				switch (button) {
				case DialogInterface.BUTTON1:
					if (resource.getType() == Type.MAP) {
						getController().loadModel(resource.getFilePath());
					} else if (resource.getType() == Type.DRAWER) {
						onChangeDrawerMenuItemSelected();
					}
					break;
				}
			}
		};
		new AlertDialog.Builder(context).setIcon(R.drawable.star_big_on)
				.setTitle(R.string.download_completed_title).setPositiveButton(
						"Yes", onClickListener).setNegativeButton("No",
						onClickListener).show();
	}

	public void onDownloadFailed() {
		Toast.makeText(context, "error downloading resource",
				Toast.LENGTH_SHORT).show();
		setApplicationTitle(null);
	}

	public void onDownloadStart() {
		setApplicationTitle("Downloading");
	}

	/**
	 * drawer preparing started, so show loaderDialog
	 */
	public void onDrawerPrepareStart() {
		setApplicationTitle("Preparing to draw");
		this.loaderDialog.show(false);
	}

	/**
	 * metro screen completed drawer preparing, so close loaderDialog
	 */
	public void onDrawerPrepareSuccess() {
		setApplicationTitle(null);
		this.loaderDialog.show(true);
		this.oldScreen.redraw(false);
		Runnable r = new Runnable() {
			public void run() {
				loaderDialog.hide();
			}
		};
		currentScreen.prepare();
		getHandler().postDelayed(r, 1000);
	}

	/**
	 * metro drawer preparing has failed
	 */
	public void onDrawerPrepareFailure(String reason) {
		loaderDialog.hide();

		Toast.makeText(
				context,
				"an error occured during MetroDrawer preparation"
						+ "\nwe'll try to restore a previous one",
				Toast.LENGTH_LONG).show();

		setApplicationTitle(null);
	}

	/**
	 * this would be called when the model changed
	 */
	public void onModelStateChanged(MetroModelState state) {
		MetroModelState currentState = getModel().getState();
		if (state.getAction() != MetroModelState.Action.IDLE) {
			this.infoDialog.hide();
			this.loaderDialog.show(state);
			if (state.getAction() == MetroModelState.Action.BUILDING) {
				setApplicationTitle("Building the Model");
			} else if (state.getAction() == MetroModelState.Action.READING) {
				setApplicationTitle("Reading file");
			}
		} else if (currentState.isReady()) {
			// TODO unexpected behavior if state is too out-of-date
			setApplicationTitle("Preparing to draw");
			this.loaderDialog.show(false);
			drawer.prepare(null, getModel().getData(), currentScreen.getMode(),
					this);
		}
	}

	/**
	 * this will fire when the router would enter a new state
	 */
	public void onRouterStateChanged(MetroRouterState state) {
		this.infoDialog.onRouterStateChanged(state);
		if (state.getAction() != MetroRouterState.Action.IDLE) {
			setApplicationTitle("Searching Routes");
		} else {
			setApplicationTitle(null);
		}
	}

	/**
	 * fires when user clicked a station on the metroScreenView
	 * 
	 * @param stations
	 *            found station or null if it's not found
	 */
	public void onStationRetrieved(Station[] stations) {
		Logger.log("Stations retrieved, total: " + stations.length);
		infoDialog.processStations(stations);
	}

	/**
	 * fires when controller finished retrieving routes
	 * 
	 * @param station
	 *            found station or null if it's not found
	 */
	public void onRoutesRetrieved(Route[] routes) {
		log("routes retrieved: total " + routes.length);
		this.infoDialog.processRoutes(routes);
	}

	/**
	 * should be fired from RouterInfoDialog
	 */
	public void onRouteFromDefine(Station fromStation) {
		getController().prepareRouter(fromStation);
	}

	/**
	 * should be fired from RouterInfoDialog
	 */
	public void onRouteToDefine(Station toStation) {
		getController().searchRoutes(toStation);
	}

	/**
	 * should be fired from RouterInfoDialog
	 */
	public void onRouteSelect(final Route route) {
		getController().post(new Runnable() {
			public void run() {
				if (route != null) {
					drawer.selectRoute(route.getTunnels(),
							route.getTransfers(), route.getMultiLineStations());
				} else {
					drawer.deselectRoute();
				}
				oldScreen.redraw(false);
			}
		});
	}

	/**
	 * should be fired from RouterInfoDialog
	 */
	public void onStationSelect(Station station) {
		if (station != null) {
			drawer.selectStation(station);
		} else {
			drawer.deselectStation();
		}
		oldScreen.redraw(false);
	}

	/**
	 * should be fired from RouterInfoDialog
	 */
	public void onDialogHide() {
	}

	/**
	 * this will called when zoom menu item selected
	 */
	public void onZoomMenuItemSelected() {
		oldScreen.showZoom();
	}

	/**
	 * show about application dialog and some info about the model
	 */
	public void onAboutMenuItemSelected() {
		this.aboutDialog.show(getModel().getData());

	}

	public void onSettingsMenuItemSelected() {
		new AlertDialog.Builder(getContext()).setIcon(R.drawable.star_big_on)
				.setTitle("Select an option below").setItems(R.array.settings,
						new DialogInterface.OnClickListener() {
							public void onClick(DialogInterface dialog,
									int which) {
								switch (which) {
								case 0:
									if (currentScreen == oldScreen) {
										setScreen(mapScreen);
									} else {
										setScreen(oldScreen);
									}
									break;

								case 1:
									drawerOptionsDialog.show(MetroDrawerWrapper
											.getInstance().getDrawer());
									break;

								default:
									break;
								}
							}
						}).show();
	}

	public Screen getCurrentScreen() {
		return currentScreen;
	}

	public Handler getHandler() {
		return getController();
	}

	/**
	 * @return the context
	 */
	protected NightPiter getContext() {
		return context;
	}

	/**
	 * @return the model
	 * @see com.nightpiter.NightPiter#getModel()
	 */
	protected MetroModel getModel() {
		return context.getModel();
	}

	/**
	 * @return the controller
	 * @see com.nightpiter.NightPiter#getController()
	 */
	protected MetroController getController() {
		return context.getController();
	}

}
